package baiduocr

import (
	"bytes"
	"encoding/base64"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
)

type Client struct {
	appID     string
	apiKEY    string
	secretKEY string
	token     *Token
}

func NewClient(id, apiKey, secKey string) (res *Client, err error) {
	res = &Client{appID: id, apiKEY: apiKey, secretKEY: secKey}
	err = res.Initial()
	if err != nil {
		return nil, err
	}
	return res, nil
}

type TLocation struct {
	Width  int `json:"width"`
	Top    int `json:"top"`
	Left   int `json:"left"`
	Height int `json:"height"`
}

type TWordsResult struct {
	Location TLocation `json:"location"`
	Words    string    `json:"words"`
}

type Result struct {
	LogId          uint64         `json:"log_id"`
	WordsResultNum int            `json:"words_result_num"`
	WordsResult    []TWordsResult `json:"words_result"`
	ErrorCode      int            `json:"error_code"`
	ErrorMsg       string         `json:"error_msg"`
}

//Accurate 高精度识别
func (s *Client) Accurate(image []byte) (res *Result, err error) {
	var acUrl = "https://aip.baidubce.com/rest/2.0/ocr/v1/accurate"

	var aUrl = fmt.Sprintf("%s?access_token=%s", acUrl, s.token.AccessToken)

	var data = bytes.NewBufferString("")

	v := url.Values{}
	imgEnc := make([]byte, base64.StdEncoding.EncodedLen(len(image)))
	base64.StdEncoding.Encode(imgEnc, image)

	v.Add("image", string(imgEnc))

	data.WriteString(v.Encode())

	//fmt.Println(data.Len())

	resp, err := http.DefaultClient.Post(aUrl, "application/x-www-form-urlencoded", data)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	buf := make([]byte, 500)
	build1 := bytes.NewBufferString("")
	for {
		n, _ := io.ReadFull(resp.Body, buf)
		if n > 0 {
			build1.Write(buf[:n])
		} else {
			break
		}
	}

	res = new(Result)
	err = json.Unmarshal(build1.Bytes(), res)
	if err != nil {
		return nil, err
	}

	return res, nil
}

//General 通用识别
func (s *Client) General(image []byte) (res *Result, err error) {
	var acUrl = "https://aip.baidubce.com/rest/2.0/ocr/v1/general"

	var aUrl = fmt.Sprintf("%s?access_token=%s", acUrl, s.token.AccessToken)

	var data = bytes.NewBufferString("")

	v := url.Values{}
	imgEnc := make([]byte, base64.StdEncoding.EncodedLen(len(image)))
	base64.StdEncoding.Encode(imgEnc, image)

	v.Add("image", string(imgEnc))

	data.WriteString(v.Encode())

	//fmt.Println(data.Len())

	resp, err := http.DefaultClient.Post(aUrl, "application/x-www-form-urlencoded", data)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()
	buf := make([]byte, 500)
	build1 := bytes.NewBufferString("")
	for {
		n, _ := io.ReadFull(resp.Body, buf)
		if n > 0 {
			build1.Write(buf[:n])
		} else {
			break
		}
	}

	res = new(Result)
	err = json.Unmarshal(build1.Bytes(), res)
	if err != nil {
		return nil, err
	}

	return res, nil
}

func (s *Client) Initial() error {
	var err error
	s.token, err = s.getToken()
	if err != nil {
		return err
	}
	//fmt.Println(s.token.AccessToken)
	if s.token.AccessToken == "" {
		return errors.New("Error get token")
	}
	return nil
}

type Token struct {
	RefreshToken  string `json:"refresh_token"`
	ExpiresIn     int    `json:"expires_in"`
	SessionKey    string `json:"session_key"`
	AccessToken   string `json:"access_token"`
	Scope         string `json:"scope"`
	SessionSecret string `json:"session_secret"`
}

func (s *Client) getToken() (token *Token, err error) {
	/*获取Access Token
	请求URL数据格式

	向授权服务地址https://aip.baidubce.com/oauth/2.0/token发送请求（推荐使用POST），并在URL中带上以下参数：

	grant_type： 必须参数，固定为client_credentials；
	client_id： 必须参数，应用的API Key；
	client_secret： 必须参数，应用的Secret Key；*/
	var tkUrl = "https://aip.baidubce.com/oauth/2.0/token"
	var grantType = "client_credentials"
	var reqUrl = fmt.Sprintf("%s?grant_type=%s&client_id=%s&client_secret=%s", tkUrl, grantType, s.apiKEY, s.secretKEY)
	client := &http.Client{}
	req, err := http.NewRequest("GET", reqUrl, nil)
	if err != nil {
		return token, err
	}
	req.Header.Add("Content-Type", "application/json; charset=UTF-8")
	resp, err := client.Do(req)
	if err != nil {
		return token, err
	}
	defer resp.Body.Close()
	buf := make([]byte, 500)
	build1 := bytes.NewBufferString("")
	for {
		n, _ := io.ReadFull(resp.Body, buf)
		if n > 0 {
			build1.Write(buf[:n])
		} else {
			break
		}
	}

	token = new(Token)
	err = json.Unmarshal(build1.Bytes(), token)
	return token, err
}

func (res *Result) getLeftMiddle() int {
	var max, min int
	if len(res.WordsResult) == 0 {
		return 0
	} else {
		min = res.WordsResult[0].Location.Left
		max = min
	}
	for _, v := range res.WordsResult {
		if min > v.Location.Left {
			min = v.Location.Left
		}
		if max < v.Location.Left {
			max = v.Location.Left
		}
	}
	return (max + min) / 2
}

func (res *Result) getDistMiddle() int {
	var max, min int
	var last int
	if len(res.WordsResult) < 2 {
		return 0
	} else {
		last = res.WordsResult[1].Location.Top
		min = last - res.WordsResult[0].Location.Top
		max = min
	}
	for i := 2; i < len(res.WordsResult); i++ {
		dist := res.WordsResult[i].Location.Top - last
		last = res.WordsResult[i].Location.Top
		if min > dist {
			min = dist
		}
		if max < dist {
			max = dist
		}
	}
	if (max - min) > 10 {
		return (max + min) / 2
	} else {
		return max
	}
}

func (res *Result) String() string {
	dm := res.getDistMiddle()
	lm := res.getLeftMiddle()
	out := &strings.Builder{}
	var last int
	if len(res.WordsResult) > 0 {
		last = res.WordsResult[0].Location.Top
		//fmt.Printf("%s", res.WordsResult[0].Words)
		out.WriteString(res.WordsResult[0].Words)
	}
	for i := 1; i < len(res.WordsResult); i++ {
		d := res.WordsResult[i].Location.Top - last
		last = res.WordsResult[i].Location.Top
		if d > dm {
			//fmt.Printf("\n%s", res.WordsResult[i].Words)
			out.WriteString("\n" + res.WordsResult[i].Words)
		} else if res.WordsResult[i].Location.Left <= lm {
			//fmt.Printf("%s", res.WordsResult[i].Words)
			out.WriteString(res.WordsResult[i].Words)
		} else {
			//fmt.Printf("\n%s", res.WordsResult[i].Words)
			out.WriteString("\n" + res.WordsResult[i].Words)
		}
	}
	//fmt.Println("")
	out.WriteString("\n")
	return out.String()
}
